﻿using System;
using System.Drawing;
using System.IO;
using System.Text;

namespace Gargoyle.Roton
{
    /// <summary>
    /// The palettes supported by the FontLoader class.
    /// </summary>
    public enum PaletteType
    {
        Unknown,
        RawVGA,
        UPAL
    }

    /// <summary>
    /// Information that can be read from the font file.
    /// </summary>
    public struct PaletteData
    {
        /// <summary>
        /// The raw palette data.
        /// </summary>
        public Color[] Palette;

        /// <summary>
        /// The format of the file containing the desired palette.
        /// </summary>
        public PaletteType Type;

        /// <summary>
        /// Indicates whether or not the data in Palette is valid and usable.
        /// </summary>
        public bool Usable;
    }

    public static class PaletteLoader
    {
        private const int NUM_COLORS = 16;

        private static FileStream fs;
        private static BinaryReader br;

        private static  int[] UPALMap = { 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x3C, 0x15, 0xA8, 0xAB, 0xAE, 0xB1, 0xB4, 0xB7, 0xBA, 0xBD };

        private static void CloseFile()
        {
            try
            {
                br.Close();
                fs.Close();
            }
            catch { }
        }

        private static PaletteData GetData(PaletteType type)
        {
            PaletteData data = new PaletteData();
            data.Type = type;
            data.Usable = true;

            switch(type)
            {
                case PaletteType.RawVGA:
                    data.Palette = ReadRawVGA();
                    break;
                case PaletteType.UPAL:
                    data.Palette = ReadUPAL();
                    break;
                default:
                    // the palette file is not usable
                    data.Usable = false;
                    break;
            }

            return data;
        }

        /// <summary>
        /// Reads a font file and returns data about the palette contained within, if any.
        /// </summary>
        /// <param name="Filename">The name of the palette file that should be read.</param>
        /// <returns>A structure containing data about the specified palette file.</returns>
        public static PaletteData GetPaletteData(string Filename)
        {
            PaletteData data = new PaletteData();

            try
            {
                OpenFile(Filename);
                data = GetData(GetPaletteType());
            }
            catch
            {
                // Exceptions should be handled by the calling method.
                throw;
            }
            finally
            {
                CloseFile();
            }

            return data;
        }

        /// <summary>
        /// Detects the format of the file containing the desired palette.
        /// </summary>
        /// <param name="Filename">The name of the palette file that should be read.</param>
        /// <returns>The type of palette that is detected.</returns>
        public static PaletteType GetPaletteType(string Filename)
        {
            PaletteType type = new PaletteType();

            try
            {
                OpenFile(Filename);
                type = GetPaletteType();
            }
            catch
            {
                // Exceptions should be handled by the calling method.
                throw;
            }
            finally
            {
                CloseFile();
            }

            return type;
        }

        private static PaletteType GetPaletteType()
        {
            if(IsUPAL()) return PaletteType.UPAL;
            if(IsRawVGA()) return PaletteType.RawVGA;

            return PaletteType.Unknown;
        }

        private static bool IsRawVGA()
        {
            /*
             * This simply checks to see if the file is 48 bytes in length.
             */
            return fs.Length == 48;
        }

        private static bool IsUPAL()
        {
            /*
             * This simply checks to see if the file is 192 bytes in length.
             */
            return fs.Length == 192;
        }

        private static void OpenFile(string Filename)
        {
            try
            {
                fs = new FileStream(Filename, FileMode.Open, FileAccess.Read, FileShare.Read);
                br = new BinaryReader(fs);
            }
            catch
            {
                throw;
            }
        }

        private static Color[] ReadRawVGA()
        {
            byte[] data = new byte[fs.Length];
            Color[] palette = new Color[NUM_COLORS];

            // read the entire file
            fs.Seek(0x00, SeekOrigin.Begin);
            data = br.ReadBytes((int)fs.Length);

            // VGA uses 6-bits per pixel palettes; convert these to 8bpp
            for(int i = 0; i < data.Length; i++)
                data[i] *= 4;

            // create the palette
            for(int i = 0; i < NUM_COLORS; i++)
            {
                int offset = i * 3;
                palette[i] = Color.FromArgb((int)data[offset], (int)data[offset + 1], (int)data[offset + 2]);
            }

            return palette;
        }

        private static Color[] ReadUPAL()
        {
            // UPAL is a weird format. A mapping table is required to read the file properly.
            // We'll read the entire file and seek through memory to get the values.
            byte[] data = new byte[fs.Length];
            Color[] palette = new Color[NUM_COLORS];
            MemoryStream memData;

            fs.Seek(0x00, SeekOrigin.Begin);
            data = br.ReadBytes((int)fs.Length);

            memData = new MemoryStream(data);

            for(int i = 0; i < NUM_COLORS; i++)
            {
                memData.Seek(UPALMap[i], SeekOrigin.Begin);
                palette[i] = Color.FromArgb(
                    memData.ReadByte() * 4,  // red
                    memData.ReadByte() * 4,  // green  (all of these need to be
                    memData.ReadByte() * 4   // blue    converted from 6bpp to 8bpp)
                );
            }

            memData.Close();

            return palette;
        }
    }
}
